{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Note\n",
    "\n",
    "Please view the [README](https://github.com/deeplearning4j/dl4j-examples/tree/overhaul_tutorials/tutorials/README.md) to learn about installing, setting up dependencies, and importing notebooks in Zeppelin"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Background\n",
    "\n",
    "When training neural networks, it is important to avoid overfitting the training data. Overfitting occurs when the neural network learns the noise in the training data and thus does not generalize well to data it has not been trained on. One hyperparameter that affects whether the neural network will overfit or not is the number of epochs or complete passes through the training split. If we use too many epochs, then the neural network is likely to overfit. On the other hand, if we use too few epochs, the neural network might not have the chance to learn fully from the training data.\n",
    "\n",
    "Early stopping is one mechanism used to manually set the number of epochs to prevent underfitting and overfitting. The idea behind early stopping is intuitive. First the data is split into training and testing sets. At the end of each epoch, the neural network is evaluated on the test set. If the neural network outperforms the previous best model, then we save the neural network. The best overall model is then taken to be the final model. \n",
    "\n",
    "In this tutorial we will show how to use early stopping with deeplearning4j (DL4J). We will apply the method on a feed forward neural network using the MNIST dataset, which is a dataset consisting of handwritten digits.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "import org.apache.commons.io.FilenameUtils;\n",
    "import org.nd4j.linalg.activations.Activation\n",
    "import org.nd4j.linalg.dataset.api.iterator.DataSetIterator\n",
    "import org.deeplearning4j.datasets.iterator.impl.MnistDataSetIterator\n",
    "import org.deeplearning4j.earlystopping.EarlyStoppingConfiguration;\n",
    "import org.deeplearning4j.earlystopping.EarlyStoppingModelSaver;\n",
    "import org.deeplearning4j.earlystopping.EarlyStoppingResult;\n",
    "import org.deeplearning4j.earlystopping.saver.LocalFileModelSaver;\n",
    "import org.deeplearning4j.earlystopping.scorecalc.DataSetLossCalculator;\n",
    "import org.deeplearning4j.earlystopping.termination.MaxEpochsTerminationCondition;\n",
    "import org.deeplearning4j.earlystopping.termination.MaxTimeIterationTerminationCondition;\n",
    "import org.deeplearning4j.earlystopping.trainer.EarlyStoppingTrainer;\n",
    "import org.deeplearning4j.eval.Evaluation\n",
    "import org.deeplearning4j.nn.api.OptimizationAlgorithm\n",
    "import org.deeplearning4j.nn.conf.MultiLayerConfiguration\n",
    "import org.deeplearning4j.nn.conf.NeuralNetConfiguration\n",
    "import org.deeplearning4j.nn.conf.Updater\n",
    "import org.deeplearning4j.nn.conf.layers.DenseLayer\n",
    "import org.deeplearning4j.nn.conf.layers.OutputLayer\n",
    "import org.deeplearning4j.nn.multilayer.MultiLayerNetwork\n",
    "import org.deeplearning4j.nn.weights.WeightInit\n",
    "import org.deeplearning4j.optimize.listeners.ScoreIterationListener\n",
    "import org.nd4j.linalg.api.ndarray.INDArray\n",
    "import org.nd4j.linalg.dataset.DataSet\n",
    "import org.nd4j.linalg.lossfunctions.LossFunctions.LossFunction\n",
    "import org.slf4j.Logger\n",
    "import org.slf4j.LoggerFactory\n",
    "\n",
    "import java.io.File;\n",
    "import java.util.concurrent.TimeUnit;\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we have imported everything needed to run this tutorial, we can start by setting the parameters for the neural network and initializing the data. We will set the maximum number of epochs to run early stopping on to be 15."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val numRows = 28\n",
    "val numColumns = 28\n",
    "val outputNum = 10 \n",
    "val batchSize = 128\n",
    "val rngSeed = 123\n",
    "\n",
    "val mnistTrain: DataSetIterator = new MnistDataSetIterator(batchSize, true, rngSeed)\n",
    "val mnistTest: DataSetIterator = new MnistDataSetIterator(batchSize, false, rngSeed)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we will set the neural network configuration using the MultiLayerNetwork class of DL4J and initialize the MultiLayerNetwork."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val conf : MultiLayerConfiguration = new NeuralNetConfiguration.Builder()\n",
    "        .seed(rngSeed) //include a random seed for reproducibility\n",
    "        // use stochastic gradient descent as an optimization algorithm\n",
    "        .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)\n",
    "        .iterations(1)\n",
    "        .learningRate(0.006) //specify the learning rate\n",
    "        .updater(Updater.NESTEROVS)\n",
    "        .regularization(true).l2(1e-4)\n",
    "        .list()\n",
    "        .layer(0, new DenseLayer.Builder() //create the first, input layer with xavier initialization\n",
    "                .nIn(numRows * numColumns)\n",
    "                .nOut(1000)\n",
    "                .activation(Activation.RELU)\n",
    "                .weightInit(WeightInit.XAVIER)\n",
    "                .build())\n",
    "        .layer(1, new OutputLayer.Builder(LossFunction.NEGATIVELOGLIKELIHOOD) //create hidden layer\n",
    "                .nIn(1000)\n",
    "                .nOut(outputNum)\n",
    "                .activation(Activation.SOFTMAX)\n",
    "                .weightInit(WeightInit.XAVIER)\n",
    "                .build())\n",
    "        .pretrain(false).backprop(true) //use backpropagation to adjust weights\n",
    "        .build()\n",
    "                \n",
    "val model : MultiLayerNetwork = new MultiLayerNetwork(conf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we weren't using early stopping, we would proceed by training the neural network using for loops and the fit method of the MultiLayerNetwork. But since we are using early stopping we need to configure how early stopping will be applied. Looking at the next cell, we will use a maximum epoch number of 10 and a maximum training time of 5 minutes. The evaluation will be done on mnistTest after each epoch. Each model will be saved in the DL4JEarlyStoppingExample directory that we specified.\n",
    "\n",
    "Once the EarlyStoppingConfiguration is specified, we only need to initialize an EarlyStoppingTrainer using the training data and the two previous configuraitons. The results are obtained just by calling the fit method of EarlyStoppingTrainer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val tempDir : String = System.getProperty(\"java.io.tmpdir\")\n",
    "val exampleDirectory : String = FilenameUtils.concat(tempDir, \"DL4JEarlyStoppingExample/\")\n",
    "val dirFile : File = new File(exampleDirectory)\n",
    "dirFile.mkdir()\n",
    "\n",
    "val saver  = new LocalFileModelSaver(exampleDirectory)\n",
    "\n",
    "val esConf  = new EarlyStoppingConfiguration.Builder()\n",
    "\t\t.epochTerminationConditions(new MaxEpochsTerminationCondition(10))\n",
    "\t\t.iterationTerminationConditions(new MaxTimeIterationTerminationCondition(5, TimeUnit.MINUTES))\n",
    "\t\t.scoreCalculator(new DataSetLossCalculator(mnistTest, true))\n",
    "        .evaluateEveryNEpochs(1)\n",
    "\t\t.modelSaver(saver)\n",
    "\t\t.build()\n",
    "\n",
    "val trainer  = new EarlyStoppingTrainer(esConf,conf,mnistTrain)\n",
    "val result = trainer.fit()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can then print out the details of the best model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "println(\"Termination reason: \" + result.getTerminationReason())\n",
    "println(\"Termination details: \" + result.getTerminationDetails())\n",
    "println(\"Total epochs: \" + result.getTotalEpochs())\n",
    "println(\"Best epoch number: \" + result.getBestModelEpoch())\n",
    "println(\"Score at best epoch: \" + result.getBestModelScore())"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Spark 2.0.0 - Scala 2.11",
   "language": "scala",
   "name": "spark2-scala"
  },
  "language_info": {
   "codemirror_mode": "text/x-scala",
   "file_extension": ".scala",
   "mimetype": "text/x-scala",
   "name": "scala",
   "pygments_lexer": "scala",
   "version": "2.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
